package demo.miner;

import java.awt.Color;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jtech.Drawer;
import jtech.Inputs;
import jtech.math.Vec2f;
import demo.miner.ClassEntity.ClassName;
import demo.miner.system.Objects;

public class Game {
	
	public static final int GRID_SIZE = 32;
	
	public static final Color DRAG_BORDER_COLOR = new Color(255, 255, 255, 128);
	public static final Color DRAG_FILL_COLOR = new Color(255, 255, 255, 15);
	
	public static final Color SELECTION_BORDER_COLOR = new Color(255, 255, 0, 128);
	public static final Color SELECTION_FILL_COLOR   = new Color(255, 215, 0, 51);
    
	public static final Color HEALTH_BAR_BORDER_COLOR       = new Color(0, 0, 0, 204);
	public static final Color HEALTH_BAR_HEALTHY_FILL_COLOR = new Color(0, 255, 0, 128);
	public static final Color HEALTH_BAR_DAMAGED_FILL_COLOR = new Color(255, 0, 0, 128);
	public static final int   HEALTH_BAR_HEIGHT             = 3;

	public static boolean debug = false;
	
	private static final Comparator<Entity<?>> DRAW_SORTED = new Comparator<Entity<?>>() {
	        
        public int compare(Entity<?> o1, Entity<?> o2) {
            float o1x = o1.getCenterX();
            float o1y = o1.getCenterY();
            float o2x = o2.getCenterX();
            float o2y = o2.getCenterY();
            float value = (o1y - o2y + ( (o2y == o1y) ? (o1x - o2x) : 0));
            return value > 0 ? 1 : (value < 0 ? -1 : 0);
        }
        
    };
	
    
    private int nextId;
    
    private int mapGridWidth;
    private int mapGridHeight;
    private int[][] mapObstructedTerrain;
	private boolean[][] mapPassableGrid;
	
	
    private boolean rebuildMapPassableGrid;
    private AStar astar;
	
    private Map<Integer, Entity<?>> entityMap = new HashMap<Integer, Entity<?>>();
	private ArrayList<Entity<?>> entities = new ArrayList<Entity<?>>();
	private ArrayList<Entity<?>> selectedEntities = new ArrayList<Entity<?>>();
	private ArrayList<Entity<?>> sortedEntities = new ArrayList<Entity<?>>();
	
	private ArrayList<EntityUnit> units = new ArrayList<EntityUnit>();
	private ArrayList<EntityBuilding> buildings = new ArrayList<EntityBuilding>();
	
    private boolean drag;
    private int dragX, dragY;
    private Rectangle2D dragRect = new Rectangle2D.Float(0, 0, 0, 0);
    
    private int gameX, gameY;
    private int gridX, gridY;
    private int offsetX, offsetY;
	
    private Team teamPlayer;
    
    private boolean[] mouseButton = new boolean[3];
	
	private Rectangle2D.Float windowRect = new Rectangle2D.Float();
   
    public void createMap(int mapGridWidth, int mapGridHeight) {
    	this.mapGridWidth = mapGridWidth;
    	this.mapGridHeight = mapGridHeight;
    	
    	mapPassableGrid = new boolean[mapGridHeight][];
    	for (int y = 0; y < mapGridHeight; y++) {
			mapPassableGrid[y] = new boolean[mapGridWidth];
    	}
    		
    	astar = new AStar(mapPassableGrid, GRID_SIZE);
    }
    
    @SuppressWarnings("unchecked")
	public <T extends Entity<?>> T createEntity(ClassName name, Team team, Object ... props) {
    	ClassEntity entityClass = ClassEntity.get(name);
    	
    	Entity<?> entity = entityClass.newInstance();
    	entity.setTeam(team);
    	if (props != null && props.length > 0 && props.length % 2 == 0) {
    		Objects.set(entity, props);
    	}
    	
    	add(entity);
    	
    	return (T) entity;
    }
    
	public void add(Entity<?> entity) {
		entity.setId(nextId++);
		
		entities.add(entity);
		sortedEntities.add(entity);
		
		entityMap.put(entity.getId(), entity);
		
		if (entity.isUnit()) {
			units.add(entity.asUnit());
		}
		
		if (entity.isBuilding()) {
			buildings.add(entity.asBuilding());
			rebuildMapPassableGrid = true;
		}
		
	}
	
	public void remove(Entity<?> e) {
		e.setSelected(false);
		
		entities.remove(e);
		selectedEntities.remove(e);
		entityMap.remove(e.getId());
		
		if (e.isBuilding()) {
            buildings.remove(e);
            rebuildMapPassableGrid = true;
        }
        
        if (e.isUnit()) {
            units.remove(e);
        }
		
	}
	
	private void unobstructedPassableGrid() {
    	for (int y = 0; y < mapGridHeight; y++) {
			Arrays.fill(mapPassableGrid[y], true);
    	}
    }

    private void obstructedPassableGrid(Entity<?> entity) {
    	if (!entity.isBuilding()) {
    		return;
    	}
    	
        int[][] grid = entity.asBuilding().getPassableGrid().getValue();

        for (int y = 0; y < grid.length; y++) {
            for (int x = 0; x < grid[y].length; x++) {
                int x2 = (int) entity.getPosition().x / GRID_SIZE + x;
                int y2 = (int) entity.getPosition().y / GRID_SIZE + y;
                if (x < 0 && x > mapGridWidth-1 && y < 0 && y > mapGridHeight-1) {
                	throw new IllegalArgumentException("index of bound: " + x2 + "," + y2);
                }
                if (grid[y][x] == 1) {
                    mapPassableGrid[y2][x2] = false;
                }
            }
        }
    }
	
	public void rebuildPassableGrid() {
		unobstructedPassableGrid();
		for (Entity<?> building : buildings) {
			obstructedPassableGrid(building);
		}
	}
	
	public void reset() {
		nextId = 0;
		entities.clear();
		selectedEntities.clear();
		sortedEntities.clear();
		units.clear();
		buildings.clear();
	}
	
	public void cleanSelectedEntities() {
		for (Entity<?> entity : selectedEntities) {
			entity.setSelected(false);
		}
		selectedEntities.clear();
    }
    
	public void selectedEntity(Entity<?> entity, boolean shiftPressed) {
		// Pressing shift and clicking on a selected item will deselect it
		if (shiftPressed && entity.isSelected()) {
			// deselect item
			entity.setSelected(false);
			for (int i = selectedEntities.size() - 1; i >= 0; i--) {
				if (selectedEntities.get(i).getId() == entity.getId()) {
					selectedEntities.remove(i);
					break;
				}
			}
			return;
		}
		if (entity.isSelectable() && !entity.isSelected()) {
			entity.setSelected(true);
			selectedEntities.add(entity);
		}
	}

	public Entity<?> getEntityUnderMouse() {
    	for (int i = entities.size() - 1; i >= 0; i--) {
	    	Entity<?> entity = entities.get(i);
    		if (!entity.isDead() && entity.isMouseOver()) {
    			return entity;
    		}
	    }
    	return null;
	}
	
	public Entity<?> getEntityById(int id) {
		return entityMap.get(id);
	}
	
    private void drawDragSelect(Drawer drawer) {
        if (drag) {    
            int x = Math.min(gameX, dragX) - offsetX;
            int y = Math.min(gameY, dragY) - offsetY;
            int w = Math.abs(gameX - dragX);
            int h = Math.abs(gameY - dragY);
            drawer.color(DRAG_FILL_COLOR).rectf(x, y, w, h);
            drawer.color(DRAG_BORDER_COLOR).rect(x, y, w, h);
        }
    }
	
    private void drawEntities(Drawer drawer, List<Entity<?>> entities) {
        for (Entity<?> e : entities) {
            e.drawSelection(this, drawer);
        }
        for (Entity<?> e : entities) {
//            e.drawImage(this, drawer);
        }
        for (Entity<?> e : entities) {
            e.drawLifeBar(this, drawer);
        }
        if (debug) {
            for (Entity<?> e : entities) {
                e.drawDebug(this, drawer);
            }
        }
    }
    
	public void draw(Drawer drawer) {
		if (debug) {
            astar.debug(drawer, true, true);
        }
        
		drawEntities(drawer, sortedEntities);
        
        drawDragSelect(drawer);
	}
	
	public void update(float elapsedTime) {
		
		if (rebuildMapPassableGrid) {
			rebuildPassableGrid();
		}
		
		sortedEntities.clear();
		
        for (int i = entities.size() - 1; i >= 0; i--) {
        	Entity<?> entity = entities.get(i);
        	entity.update(this, elapsedTime);
        	if (canDraw(entity)) {
        		sortedEntities.add(entity);
        	}
        }
        
        Collections.sort(sortedEntities, DRAW_SORTED);
        
        rebuildMapPassableGrid = false;
        
	}
	
	private Rectangle2D setDragRect() {
		int x1 = Math.min(gameX, dragX);
        int y1 = Math.min(gameY, dragY);
        int x2 = Math.max(gameX, dragX);
        int y2 = Math.max(gameY, dragY);
        dragRect.setRect(x1, y1, x2-x1, y2-y1);
        return dragRect;
	}
	
	private List<Integer> findEntities(List<?> entities, Team team, Class<?> entityClass, Boolean canAttack) {
		List<Integer> ids = new ArrayList<Integer>();
        //identify selected items from players team that can move
        for (Entity<?>  entity : selectedEntities) {
            if(team != null && entity.getTeam() != team) {
            	continue;
            }
            if(entityClass != null && !entity.getClassName().equals(entityClass.getCanonicalName())) {
            	continue;
            }
            if(canAttack != null && entity.isCanAttack() != canAttack) {
            	continue;
            }
            ids.add(entity.getId());
        }
        return ids;
	}
	
	
	public void action(GameMain gameMain) {
		
		gameX = gameMain.getMouseX() + offsetX;
		gameY = gameMain.getMouseY() + offsetY;
		
		gridX = gameX / GRID_SIZE;
		gridY = gameY / GRID_SIZE; 
		
		for (int i = 0; i < 3; i++) {
			if (gameMain.isButtonPressed(i+1)) {
				mouseButton[i] = true;
			} else if (gameMain.isButtonReleased(i+1)) {
				mouseButton[i] = false;
			}
		}

		Entity<?> clickedItem = getEntityUnderMouse();
		
		if (gameMain.isButtonPressed(Inputs.MOUSE_BUTTON_LEFT)) {
        	dragX = gameMain.getMouseX();
        	dragY = gameMain.getMouseY();
        }
		
        if (gameMain.isButtonDown(Inputs.MOUSE_BUTTON_LEFT)) {
        	if ((Math.abs(dragX - gameX) > 4 || Math.abs(dragY - gameY) > 4)) {
        		drag = true;
        		setDragRect();
        	}
        }
        
		if (gameMain.isButtonReleased(Inputs.MOUSE_BUTTON_LEFT)) {
			boolean shiftPressed = gameMain.isKeyDown(Inputs.KEY_SHIFT);
			if (!shiftPressed) {
				cleanSelectedEntities();
			}
			if (drag) {
				for (int i = entities.size() - 1; i >= 0; i--) {
					Entity<?> entity = entities.get(i);
					if (entity.isSelectionOver()) { 
						selectedEntity(entity, shiftPressed);
					}
				}
			} else {
				if (clickedItem != null && clickedItem.isSelectable() && clickedItem.getTeam() == teamPlayer) {
					selectedEntity(clickedItem, shiftPressed);
				}
			}
			drag = false;
		}

		if (gameMain.isButtonReleased(Inputs.MOUSE_BUTTON_RIGHT)) {
			if (clickedItem == null) {
				List<Integer> ids = findEntities(selectedEntities, teamPlayer, demo.miner.EntityUnit.class, null);
				int x = (gridX * GRID_SIZE) + GRID_SIZE / 2;
				int y = (gridY * GRID_SIZE) + GRID_SIZE / 2;
				processCommand(ids, Order.move(x, y));
			} else {
				if (clickedItem.getTeam() == teamPlayer) {
					List<Integer> ids = findEntities(selectedEntities, teamPlayer, demo.miner.EntityUnit.class, null);
					processCommand(ids, Order.follow(clickedItem.getId()));
				} else {
					List<Integer> ids = findEntities(selectedEntities, teamPlayer, demo.miner.EntityUnit.class, true);
					processCommand(ids, Order.attack(clickedItem.getId()));
				}
			}
			
		}
		
		for (int i = entities.size() - 1; i >= 0; i--) {
        	entities.get(i).fireEvents(this);
        }
		
    }
	
	public boolean isDrag() {
		return drag;
	}
	
	public Rectangle2D getDrawRect() {
		return dragRect;
	}
	
	public boolean isMouseOver(Entity<?> entity) {
		float x, y, r;
		if (entity.isUnit()) {
			r = entity.asUnit().getBaseRadius();
			x = entity.getX() - gameX;
			y = entity.getY() - gameY;
		} else if (entity.isBuilding() ){
			r = Math.min(entity.asBuilding().getSpriteWidth(), entity.asBuilding().getSpriteHeight()) / 2;
			x = entity.getX() + r - gameX;
			y = entity.getY() + r - gameY;
		} else {
			return false;
		}
		return !drag && ((x*x + y*y) < (r*r));
	}
	
	public boolean isDragOver(Entity<?> entity) {
		return drag
			&& entity.isSelectable()
			&& dragRect.intersects(entity.getX(), entity.getY(), 1, 1);
	}

	private boolean canDraw(Entity<?> entity) {
		float x, y, h, w;
		Vec2f pos = toPointScreen(entity.getPosition());
		if (entity.isUnit()) {
			float r = entity.getBaseRadius();
			x = pos.x - r;
			y = pos.y - r;
			w = h = r * 2;
		} else if (entity.isBuilding()) {
			x = pos.x;
			y = pos.y;
			w = entity.getSpriteWidth();
			h = entity.getSpriteHeight();
		} else {
			return false;
		}
    	return windowRect.intersects(x, y, w, h);
    }
	
    public void sendCommand(List<Integer> ids, Order order) {
        if (ids == null || ids.isEmpty()) {
            return;
        }
        singleplayer(ids, order);
    }
    
    private void singleplayer(List<Integer> ids, Order order) {
        processCommand(ids, order);
    }
    
    private void processCommand(List<Integer> ids, Order order) {
        // In case the target "to" object is in terms of uid, fetch the target object
        Entity<?> toEntity = null;
        if (order.getTargetId() != 0) {
            toEntity = entityMap.get(order.getTargetId());
            if (toEntity == null || toEntity.isDead()) {
                // To object no longer exists. Invalid command
                return;
            }
        }
        
        for (int id : ids) {
            Entity<?> entity = entityMap.get(id);
            // if uid is a valid item, set the order for the item
            if (entity != null) {
                entity.receivesOrder(order);
            }
            System.out.println("process command " + entity + " receives order " + order);
        }
    }
    
//    public ArrayList<Vec2f> getSquare(float vx, float vy, int size) {
//    	int ix = (int) (vx / Game.GRID_SIZE) - size/2;
//        int iy = (int) (vy / Game.GRID_SIZE) - size/2;
//        int fx = ix + size/2;
//        int fy = iy + size/2;
//        
//        ArrayList<Vec2f> list = new ArrayList<Vec2f>(size); 
//        
//        for (int y = iy; y <= fy; y++) {
//        	for (int x = ix; x <= fx; x++) {
//        		list.add( mapGrid.getPosition(x, y).copy() );
//        	}
//        }
//        
//        return list;
//    }

    public List<Vec2f> pathfind(Vec2f start, Vec2f goal) {
        return astar.search(start, goal);
    }
    
    public Vec2f toPointScreen(float posx, float posy) {
    	return new Vec2f(posx - offsetX,posy - offsetY);
    }
    
    public Vec2f toPointScreen(Vec2f pos) {
    	return toPointScreen(pos.x,  pos.y);
    }
	
    
	public Vec2f toMapGrid(float posx, float posy) {
        return new Vec2f(posx / Game.GRID_SIZE, posy / Game.GRID_SIZE);
    }
    
	public Vec2f toMapGrid(Vec2f pos) {
        return toMapGrid(pos.x, pos.y);
    }
    
	public boolean[] isMouseButton() {
		return mouseButton;
	}

	public Team getTeamPlayer() {
		return teamPlayer;
	}

	public void setTeamPlayer(Team teamPlayer) {
		this.teamPlayer = teamPlayer;
	}
	
	public int getGameX() {
		return gameX;
	}

	public void setGameX(int gameX) {
		this.gameX = gameX;
	}

	public int getGameY() {
		return gameY;
	}

	public void setGameY(int gameY) {
		this.gameY = gameY;
	}

	public int getOffsetX() {
		return offsetX;
	}

	public void setOffsetX(int offsetX) {
		this.offsetX = offsetX;
	}

	public int getOffsetY() {
		return offsetY;
	}

	public void setOffsetY(int offsetY) {
		this.offsetY = offsetY;
	}

	public void setWindowWidth(int width) {
		windowRect.width = width;
	}

	public void setWindowHeight(int height) {
		windowRect.height = height;
	}

	public int getWindowWidth() {
		return (int) windowRect.width;
	}

	public int getWindowHeight() {
		return (int) windowRect.height;
	}

	public boolean isRebuildMapPassableGrid() {
		return rebuildMapPassableGrid;
	}

	public List<EntityUnit> getUnits() {
		return units;
	}
	
}
